﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Text.RegularExpressions;
using System.Diagnostics;
using System.IO;

namespace Compiler
{
    public class Lexer
    {
        private List<Regex> Rules;
        private List<Token> Tokens;

        private int Pos;
        private string Source;
        private int tokenCounter = 0;

        //bool considerLiterally = false;
        //Regex BeginLiterallyRegEx, EndLiterallyRegEx;

        public Lexer(List<Token> TokenList)
        {
            this.Tokens = TokenList;
            this.Rules = Tokens.Select(token => CreateRegex(token.Pattern)).ToList();
        }

        private Regex CreateRegex(string pattern)
        {
            return new Regex(pattern, RegexOptions.Compiled);
        }

        public Lexer(List<Token> TokenList, string BeginLiterallyLexeme, string EndLiterallyLExeme)
            : this(TokenList)
        {
            if (BeginLiterallyLexeme == EndLiterallyLExeme || string.IsNullOrEmpty(BeginLiterallyLexeme) || string.IsNullOrEmpty(EndLiterallyLExeme))
                throw new ArgumentException();

            //Token tokenBeginLiterally = new Token(CreateRegex(BeginLiterallyLexeme), "BEGIN_LIT", Int32.MaxValue);
            //Token tokenEndLiterally = new Token(CreateRegex(EndLiterallyLExeme), "ENDL_IT", Int32.MaxValue);

            //TokenList.Insert(0, CreateRegex(tokenBeginLiterally));
            //TokenList.Insert(0, CreateRegex(tokenEndLiterally));
            ////considerLiterally = true;
        }

        private static int IndexOfNextCharNotWhiteSpace(string input, int start)
        {
            if (!string.IsNullOrEmpty(input))
                for (int i = start; i < input.Length; ++i)
                    if (
                        //input[i] == '\n' ||
                        !char.IsWhiteSpace(input, i))
                        return i;

            return -1;
        }

        public void SetSource(string Source)
        {
            this.Source = Source;
            Pos = IndexOfNextCharNotWhiteSpace(Source, 0);
            tokenCounter = 0;
        }

        //(1) Die Regel mit dem längeren Match gewinnt.
        //(2) Wenn (1) nicht eindeutig, gewinnt die Regel "weiter oben" in der Liste 'List<Regex> Rules'
        public LexerPair NextToken()
        {
            if (Pos != -1)
                Pos = IndexOfNextCharNotWhiteSpace(Source, Pos);

            //EOF
            if (Pos == -1 || Pos == Source.Length)
                return new LexerPair { Token = Token.EOF, Lexeme = "" };

            Match winnerMatch = null;
            Token winnerToken = null;

            int restOfSourceLength = Source.Length - Pos;

            for (int i = 0; i < Rules.Count; ++i)
            {
                Match match = Rules[i].Match(Source, Pos, Math.Min(Tokens[i].MaxSize, restOfSourceLength));
                if (match.Success && match.Index == Pos)
                {
                    if (winnerMatch == null || winnerMatch.Length < match.Length)
                    {
                        winnerToken = Tokens[i];
                        winnerMatch = match;
                    }
                }
            }

            if (winnerMatch != null)
            {
                tokenCounter++;
                string lexeme = winnerMatch.Value;
                Pos += lexeme.Length;
                return new LexerPair { Token = winnerToken, Lexeme = lexeme };
            }

            //UNKNOWN
            return new LexerPair { Token = Token.Unknown, Lexeme = GetRestOfSource() };
        }

        public string GetRestOfSource()
        {
            return Pos == -1 ? "" : Source.Substring(Pos);
        }

        public IEnumerator<LexerPair> GetEnumerator()
        {
            LexerPair lexerPair;
            while ((lexerPair = NextToken()).Token != Token.EOF && lexerPair.Token != Token.Unknown)
                yield return lexerPair;

            //EOF oder UNKNOWN
            yield return lexerPair;
        }
    }
}
